package com.spzx.common.redis.aspectj;

import cn.hutool.core.util.RandomUtil;
import com.alibaba.fastjson2.JSON;
import com.spzx.common.redis.annotation.CustomCache;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import java.lang.reflect.Method;
import java.util.concurrent.TimeUnit;

/**
 * 自定义缓存注解 切面类
 */
@Slf4j
@Aspect
@Component
public class CacheAspect {

    @Autowired
    private StringRedisTemplate redisTemplate;

    @Around(value = "@annotation(customCache)")
    public Object doAround(ProceedingJoinPoint point, CustomCache customCache) throws Throwable {
        // 1、组装redis缓存的key
        // user:region:tree
        String key = customCache.prefix();
        // #parentCode   :parentCode
        MethodSignature methodSignature = (MethodSignature) point.getSignature();
        Method method = methodSignature.getMethod();
        DefaultParameterNameDiscoverer defaultParameterNameDiscoverer = new DefaultParameterNameDiscoverer();
        String[] parameterNames = defaultParameterNameDiscoverer.getParameterNames(method);
        ExpressionParser parser = new SpelExpressionParser();
        Expression expression = parser.parseExpression(customCache.key());
        StandardEvaluationContext ctx = new StandardEvaluationContext();
        Object[] args = point.getArgs();
        for (int i = 0; i < parameterNames.length; i++) {
            ctx.setVariable(parameterNames[i], args[i]);
        }
        String value = expression.getValue(ctx, String.class);
        String cacheKey = key + ":" + value;
        // 2、根据key获取缓存的value
        String cacheValue = redisTemplate.opsForValue().get(cacheKey);
        log.info("查询缓存{}的值，结果为：{}", cacheKey, cacheValue);
        Class<?> returnType = method.getReturnType();
        if (StringUtils.hasText(cacheValue)) {
            // 2.1、如果缓存命中，将缓存的json字符串转换成方法返回值需要的类型，并响应
            Object result = JSON.parseObject(cacheValue, returnType);
            return result;
        } else {
            // 2.2、如果缓存未命中，执行原方法，查询数据库，将结果缓存到redis后，给前端响应结果
            log.info("缓存{}未命中，执行业务逻辑去获取结果", cacheKey);
            Object proceed = point.proceed();
            // 3、是否添加过期时间，过期时间是否添加随机值的判断
            long expiredTime = TimeUnit.MILLISECONDS.convert(customCache.expiredTime(), customCache.timeUnit());
            cacheValue = JSON.toJSONString(proceed);
            if (expiredTime <= 0) {
                // 3.1、未指定过期时间
                log.info("业务逻辑执行完成，保存缓存，key：{}，value：{}", cacheKey, cacheValue);
                redisTemplate.opsForValue().set(cacheKey, JSON.toJSONString(proceed));
            } else {
                // 3.2、指定了过期时间，判断是否需要添加时间随机值
                boolean randomExpired = customCache.randomExpired();
                if (randomExpired) {
                    long randomSeconds = RandomUtil.randomLong(1, 1000);
                    expiredTime += randomSeconds;
                }
                log.info("业务逻辑执行完成，保存缓存，key：{}，value：{}，ttl：{}，时间单位：ms", cacheKey, cacheValue, expiredTime);
                redisTemplate.opsForValue().set(cacheKey, cacheValue, expiredTime, TimeUnit.MILLISECONDS);
            }
            return proceed;
        }
    }
}
